<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>产品净值与收益率图表生成器(推荐Chrome浏览器打开)</title>
    
    <!-- xlsx.js库 -->
    <script src="https://cdn.sheetjs.com/xlsx-0.19.3/package/dist/xlsx.full.min.js"></script>

    <!-- plotly.js库 -->
    <script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script>

    <style>
        /* 全局样式 */
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Microsoft YaHei", sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }

        .data-source-status {
            margin-bottom: 20px;
            padding: 15px;
            background-color: #f8f9fa;
            border-radius: 4px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .config-panel {
            margin-bottom: 10px;
        }

        .config-row {
            display: flex;
            gap: 20px;
            margin-bottom: 8px;
        }

        .config-row .form-group {
            flex: 1;
            min-height: 70px; /* 统一高度 */
        }

        .form-group {
            margin-bottom: 8px;
            display: flex;
            flex-direction: column;
        }

        .form-group label {
            margin-bottom: 4px;
            color: #666;
            font-weight: 500;
        }

        /* 统一输入框样式 */
        select, input[type="number"], input[type="text"], input[type="date"] {
            height: 38px;
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            box-sizing: border-box;
            background-color: #fff;
            font-size: 14px;
            width: 100%;
            transition: all 0.2s;
        }

        select:hover, input[type="number"]:hover, input[type="text"]:hover, input[type="date"]:hover {
            border-color: #1c48a6;
        }

        select:focus, input[type="number"]:focus, input[type="text"]:focus, input[type="date"]:focus {
            border-color: #1c48a6;
            outline: none;
            box-shadow: 0 0 0 2px rgba(28,72,166,0.1);
        }

        /* 禁用状态样式 */
        select:disabled, input:disabled {
            background-color: #f0f0f0;
            cursor: not-allowed;
        }

        .date-inputs {
            display: flex;
            gap: 10px;
        }

        .date-inputs input {
            flex: 1;
        }

        .display-options {
            background-color: #f8f9fa;
            padding: 12px;
            border-radius: 4px;
            margin-bottom: 10px;
        }

        .display-options label {
            font-weight: 500;
            color: #666;
            margin-bottom: 12px;
            display: block;
        }

        .checkbox-group {
            display: flex;
            gap: 30px;
            margin-top: 10px;
            flex-wrap: wrap;
        }

        .checkbox-label {
            display: flex;
            align-items: center;
            gap: 8px;
            cursor: pointer;
            font-size: 14px;
            color: #333;
            min-width: 150px;
        }

        .checkbox-label input[type="checkbox"] {
            width: 16px;
            height: 16px;
            margin: 0;
        }

        .action-buttons {
            text-align: center;
            margin: 20px 0;
        }

        .btn {
            padding: 10px 24px;
            font-size: 14px;
            font-weight: 500;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            transition: all 0.2s;
        }

        .btn-primary {
            background-color: #1c48a6;
            color: white;
        }

        .btn-primary:hover {
            background-color: #153a85;
        }

        .btn-secondary {
            background-color: #6c757d;
            color: white;
        }

        .btn-secondary:hover {
            background-color: #5a6268;
        }

        .charts-container {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
            margin-top: 10px;
        }

        .alert {
            padding: 15px;
            margin-bottom: 20px;
            border-radius: 4px;
        }

        .alert-info {
            background-color: #e8f4fd;
            border: 1px solid #b8daff;
            color: #004085;
        }

        .alert-warning {
            background-color: #fff3cd;
            border: 1px solid #ffeeba;
            color: #856404;
        }

        /* 响应式布局调整 */
        @media (max-width: 768px) {
            .charts-container {
                grid-template-columns: 1fr;
            }

            .container {
                padding: 10px;
            }

            .data-source-status {
                flex-direction: column;
                gap: 10px;
            }
        }

        /* 增强的交互反馈 */
        .form-group:focus-within label {
            color: #1c48a6;
        }

        select:focus, input:focus {
            border-color: #1c48a6;
            outline: none;
            box-shadow: 0 0 0 2px rgba(28,72,166,0.1);
        }

        /* 图表容器增强 */
        #navChart, #yieldChart {
            min-height: 350px;
            background-color: #fff;
            border-radius: 4px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.05);
        }

        /* 修改日期选择区域的样式 */
        .date-range-inputs {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-left: 0;
            padding-left: 0;
        }

        .date-range-inputs input[type="date"] {
            width: 150px;
            padding: 6px 12px;
            font-size: 14px;
            line-height: 1.5;
            color: #495057;
            background-color: #fff;
            border: 1px solid #ced4da;
            border-radius: 4px;
            transition: all 0.2s ease-in-out;
        }

        .date-range-inputs input[type="date"]:hover,
        .date-range-inputs input[type="date"]:focus {
            color: #495057;
            background-color: #fff;
            border-color: #2952A3;  /* 使用蓝色主题色 */
            outline: none;
            box-shadow: none; 
        }

        .date-range-inputs span {
            color: #495057;
            margin: 0 5px;
        }
    </style>
</head>
<body>
    <h1 style="color: #2952A3; text-align: center; margin: 20px 0;">蓝色宝鼎系列净值及收益率曲线展示</h1>
    <div class="container">
        <!-- 数据源状态 -->
        <div class="data-source-status">
            <div id="dataStatus">
                未检测到数据源，请上传数据文件<br>
                <small style="color: #666;">支持的文件格式：CSV (.csv)<br>
                必要的列：产品简称、日期、净值、7日年化收益(%)、30日年化收益(%)、近三月年化收益(%)</small>
            </div>
            <div>
                <input type="file" id="fileInput" accept=".csv" style="display: none;">
                <button class="btn btn-primary" onclick="document.getElementById('fileInput').click()">
                    上传数据
                </button>
                <button class="btn btn-secondary" id="clearData">
                    清除数据
                </button>
            </div>
        </div>

        <!-- 配置面板 -->
        <div class="config-panel">
            <!-- 第一行：选择产品和收益率类型 -->
            <div class="config-row">
            <div class="form-group">
                <label for="productSelect">选择产品</label>
                <select id="productSelect" disabled>
                    <option value="">请先上传数据</option>
                </select>
                </div>
                <div class="form-group">
                    <label for="yieldType">收益率类型</label>
                    <select id="yieldType">
                        <option value="7日年化收益(%)">7日年化收益(%)</option>
                        <option value="30日年化收益(%)">30日年化收益(%)</option>
                        <option value="近三月年化收益(%)">近三月年化收益(%)</option>
                    </select>
                </div>
            </div>

            <!-- 第二行：时间范围选择 -->
            <div class="config-row">
            <div class="form-group">
                <label for="dateRange">时间范围选择</label>
                <select id="dateRange">
                    <option value="recent">最近天数</option>
                    <option value="custom">自定义区间</option>
                </select>
            </div>
            <div class="form-group" id="recentDaysGroup">
                <label for="recentDays">最近天数</label>
                <input type="number" id="recentDays" value="90" min="1" step="1" class="form-control">
            </div>
            <div class="form-group" id="dateRangeGroup" style="display: none;">
                <label>起止日期</label>
                <div class="date-range-inputs">
                    <input type="date" id="startDate" value="2024-08-01">
                    <span>至</span>
                <input type="date" id="endDate">
            </div>
            </div>
            </div>
        </div>

        <!-- 在显示选项后添加边距配置 -->
        <div class="config-panel">
            <div class="display-options">
                <label>显示选项</label>
                <div class="checkbox-group">
                    <label class="checkbox-label">
                        <input type="checkbox" id="showNavStart">
                        净值起点值
                    </label>
                    <label class="checkbox-label">
                    <input type="checkbox" id="showNavMax">
                        净值最大值
                    </label>
                    <label class="checkbox-label">
                    <input type="checkbox" id="showYieldMax" checked>
                        收益率最大值
                    </label>
                    <label class="checkbox-label">
                    <input type="checkbox" id="showYieldMin" checked>
                        收益率最小值
                    </label>
                    <label class="checkbox-label">
                        <input type="checkbox" id="showColoredAnnotations">
                        标注点使用不同颜色
                    </label>
                </div>
            </div>
            
            <div class="chart-config" style="display: flex; gap: 20px; margin-top: 15px;">
                <div class="form-group" style="flex: 1;">
                    <label>X轴刻度数量</label>
                    <input type="number" id="xAxisTicks" value="8" min="2" max="15" step="1"
                           onchange="ChartRenderer.config.xAxisTicks = parseInt(this.value); UIController.updateCharts();">
                </div>
                <div class="form-group" style="flex: 1;">
                    <label>净值X轴边距比例</label>
                    <input type="number" id="navMarginX" value="0.1" min="0" max="1" step="0.01"
                           onchange="ChartRenderer.config.marginX.nav = parseFloat(this.value); UIController.updateCharts();">
                </div>
                <div class="form-group" style="flex: 1;">
                    <label>收益率Y轴边距比例</label>
                    <input type="number" id="yieldMargin" value="1.0" min="0.1" max="10" step="0.1"
                           onchange="ChartRenderer.config.marginY.yield = parseFloat(this.value); UIController.updateCharts();">
                </div>
                <div class="form-group" style="flex: 1;">
                    <label>数据点大小</label>
                    <input type="number" id="markerSize" value="6" min="1" max="10" step="1"
                           onchange="ChartRenderer.config.markerSize = parseFloat(this.value); UIController.updateCharts();">
                </div>
            </div>
        </div>

        <!-- 制图及重置按钮 -->
        <div class="action-buttons">
            <button id="generateCharts" class="btn btn-primary" disabled>
                生成图表
            </button>
            <button id="resetParams" class="btn btn-secondary" style="margin-left: 20px;">
                重置参数
            </button>
        </div>

        <!-- 图表容器 -->
        <div class="charts-container">
            <div id="navChart"></div>
            <div id="yieldChart"></div>
        </div>
    </div>


<!-- 应用程序代码 -->
<script>
    // 定义全局变量存储当前数据
    let globalData = null;
    
    // 数据管理模块
    const DataManager = {
        DB_NAME: 'ChartData',
        STORE_NAME: 'data',
        DB_VERSION: 1,

        // 打开数据库连接
        async openDB() {
            return new Promise((resolve, reject) => {
                const request = indexedDB.open(this.DB_NAME, this.DB_VERSION);
                
                request.onerror = () => reject(request.error);
                
                request.onupgradeneeded = (event) => {
                    const db = event.target.result;
                    if (!db.objectStoreNames.contains(this.STORE_NAME)) {
                        db.createObjectStore(this.STORE_NAME, { keyPath: 'id' });
                    }
                };
                
                request.onsuccess = () => resolve(request.result);
            });
        },

        // 简化的事务执行方法
        async executeTransaction(mode, operation) {
            let db = null;
            try {
                db = await this.openDB();
                return await new Promise((resolve, reject) => {
                    const tx = db.transaction(this.STORE_NAME, mode);
                    const store = tx.objectStore(this.STORE_NAME);
                    
                    let request;
                    try {
                        request = operation(store);
                    } catch (err) {
                        reject(err);
                        return;
                    }

                    request.onsuccess = () => resolve(request.result);
                    request.onerror = () => reject(request.error);
                    
                    // Safari 优化：使用 setTimeout 避免堆栈溢出
                    tx.oncomplete = () => setTimeout(() => resolve(request.result), 0);
                    tx.onerror = () => reject(tx.error);
                });
            } finally {
                if (db) setTimeout(() => db.close(), 0);
            }
        },
    
        // 保存数据
        async saveData(data) {
            if (!Array.isArray(data) || data.length === 0) {
                throw new Error('无效的数据格式');
            }

            let db = null;
            try {
                db = await this.openDB();
                
                // Safari 优化：使用 setTimeout 包装异步操作
                await new Promise((resolve, reject) => {
                    setTimeout(async () => {
                        try {
                            const tx = db.transaction(this.STORE_NAME, 'readwrite');
                            const store = tx.objectStore(this.STORE_NAME);

                            // 处理数据
                            const processedData = data.map(item => ({
                                ...item,
                                日期: item['日期'].getTime()
                            }));

                            // 清除旧数据
                            await new Promise(resolveClean => {
                                const clearReq = store.clear();
                                clearReq.onsuccess = () => resolveClean();
                                clearReq.onerror = () => reject(clearReq.error);
                            });

                            // 保存新数据
                            const saveReq = store.put({
                                id: 'currentData',
                                data: processedData,
                                updateTime: Date.now()
                            });

                            saveReq.onsuccess = () => resolve();
                            saveReq.onerror = () => reject(saveReq.error);

                            tx.oncomplete = () => resolve();
                            tx.onerror = () => reject(tx.error);
                        } catch (err) {
                            reject(err);
                        }
                    }, 0);
                });
                return true;
            } catch (error) {
                console.error('保存数据失败:', error);
                throw error;
            }
        },

        // 加载数据
        async loadData() {
            try {
                const result = await this.executeTransaction('readonly', store => 
                    store.get('currentData')
                );
                
                if (result && result.data) {
                    return result.data.map(item => ({
                        ...item,
                        日期: new Date(item['日期'])
                    }));
                }
                return null;
            } catch (error) {
                console.error('加载数据失败:', error);
                throw error;
            }
        },

        // 获取更新时间
        async getUpdateTime() {
            try {
                const result = await this.executeTransaction('readonly', store => 
                    store.get('currentData')
                );
                return result ? new Date(result.updateTime) : null;
            } catch (error) {
                console.error('获取更新时间失败:', error);
                throw error;
            }
        },
    
        // 清除数据
        async clearData() {
            try {
                await this.executeTransaction('readwrite', store => 
                    store.clear()
                );
                return true;
                    } catch (error) {
                console.error('清除数据失败:', error);
                throw error;
                    }
        }
    };
    
    // 数据处理模块
    const DataProcessor = {
        // 获取产品列表
        getProductList(data) {
            return [...new Set(data.map(row => row['产品简称']))].sort();
        },
    
        // 过滤数据
        filterData(data, params) {
            let filteredData = data.filter(row => row['产品简称'] === params.productName);
            
            // 根据日期范围筛选
            if (params.dateRange === 'recent') {
                const endDate = new Date(Math.max(...filteredData.map(row => new Date(row['日期']))));
                const startDate = new Date(endDate);
                startDate.setDate(startDate.getDate() - params.recentDays);
                
                filteredData = filteredData.filter(row => {
                    const date = new Date(row['日期']);
                    return date >= startDate && date <= endDate;
                });
            } else {
                filteredData = filteredData.filter(row => {
                    const date = new Date(row['日期']);
                    return date >= new Date(params.startDate) && date <= new Date(params.endDate);
                });
            }

            // 找到第一个非零值的位置
            const firstNonZeroIndex = filteredData.findIndex(row => 
                parseFloat(row['净值']) > 0 && 
                parseFloat(row[params.yieldType]) !== 0
            );

            // 如果找到了非零值，从该位置开始截取数据
            if (firstNonZeroIndex > 0) {
                filteredData = filteredData.slice(firstNonZeroIndex);
            }

            return filteredData;
        },
    
        // 计算统计数据
        calculateStats(data, yieldType) {
            const yieldValues = data.map(row => row[yieldType]).filter(v => v != null);
            return {
                max: Math.max(...yieldValues),
                min: Math.min(...yieldValues),
                avg: yieldValues.reduce((a, b) => a + b, 0) / yieldValues.length
            };
        }
    };
    
    // 图表绘制模块
    const ChartRenderer = {
        config: {
            lineColor: '#D75241',      // 折线颜色
            annotationColor: '#1c48a6', // 标注点的默认颜色，与普通点区分
            gridColor: '#E6E6E6',      // 网格颜色
            gridAlpha: 0.5,           // 网格透明度
            titleColor: '#1c48a6',     // 标题颜色
            axisColor: '#1c48a6',      // 坐标轴颜色
            lineWidth: 2,              // 线条宽度
            markerSize: 6,             // 默认数据点大小
            labelFontSize: 14,         // 标签字体大小
            auxiliaryLabelSize: 11,    // 辅助线标签字体大小
            marginY: {
                nav: 0.20,             // 净值Y轴边距比例
                yield: 1.0             // 收益率Y轴边距比例
            },
            marginX: {
                nav: 0.1              // 修改默认值为0.1
            },
            labelOffset: {
                max: 25,
                min: -25,
                normal: 15
            },
            xAxisTicks: 8
        },

        getOptimalTicks(dates) {
            if (dates.length <= this.config.xAxisTicks) {
                return Array.from({length: dates.length}, (_, i) => i);
            }
            
            const ticks = [];
            // 使用配置的刻度数量
            for (let i = 0; i <= this.config.xAxisTicks - 1; i++) {
                const index = Math.round(i * (dates.length - 1) / (this.config.xAxisTicks - 1));
                ticks.push(index);
            }
            
            return ticks;
        },

        formatDate(date) {
            const d = new Date(date);
            const year = d.getFullYear();
            const month = String(d.getMonth() + 1).padStart(2, '0');
            const day = String(d.getDate()).padStart(2, '0');
            return `${year}/${month}/${day}`;
        },

        renderNavChart(data) {
            const showNavStart = document.getElementById('showNavStart').checked;
            const showNavMax = document.getElementById('showNavMax').checked;
            const showColoredAnnotations = document.getElementById('showColoredAnnotations').checked;
            const markerSize = parseFloat(document.getElementById('markerSize').value) || this.config.markerSize;
            const navMarginX = parseFloat(document.getElementById('navMarginX').value) || this.config.marginX.nav;
            
            const values = data.map(d => d['净值']);
            const dates = data.map(d => d['日期']);
            const xValues = Array.from({length: dates.length}, (_, i) => i);
            
            // 计算X轴范围
            const xMin = 0;
            const xMax = dates.length - 1;
            const xRange = (xMax - xMin) * navMarginX;
    
            const layout = {
                title: {
                    text: '单位净值',
                    font: {
                        size: 16,
                        color: this.config.titleColor
                    },
                    x: 0,  // 标题靠左对齐
                    xanchor: 'left'
                },
                xaxis: {
                    tickmode: 'array',
                    tickvals: this.getOptimalTicks(dates),
                    ticktext: this.getOptimalTicks(dates).map(i => this.formatDate(dates[i])),
                    tickangle: -45,
                    tickfont: {
                        color: this.config.titleColor,
                        size: 11
                    },
                    showgrid: true,
                    gridcolor: this.config.gridColor,
                    gridwidth: 1,
                    zeroline: false,
                    showline: true,
                    linecolor: this.config.titleColor,
                    linewidth: 1,
                    tickcolor: this.config.titleColor,
                    tickwidth: 1,
                    range: [xMin - xRange, xMax + xRange]
                },
                yaxis: {
                    showgrid: false,
                    zeroline: false,
                    showline: false,
                    showticklabels: false
                },
                plot_bgcolor: 'white',
                paper_bgcolor: 'white',
                margin: { t: 30, r: 10, b: 80, l: 10 }
            };

            const trace = {
                x: xValues,
                y: values,
                type: 'scatter',
                mode: 'lines+markers',
                line: {
                    color: this.config.lineColor,
                    width: this.config.lineWidth
                },
                marker: {
                    size: Array(dates.length).fill(markerSize),
                    color: Array(dates.length).fill(this.config.lineColor),
                    opacity: 1,
                    line: {
                        width: 0
                    }
                }
            };

            // 添加标注
            if (values.length > 0) {
                const annotations = [];
                const markerSizes = Array(dates.length).fill(markerSize);
                const markerColors = Array(dates.length).fill(this.config.lineColor);
                const lastIndex = values.length - 1;
                const maxIndex = values.indexOf(Math.max(...values));

                // 始终标注最新值
                annotations.push({
                    x: xValues[lastIndex],
                    y: values[lastIndex],
                    text: values[lastIndex].toString(),
                    showarrow: false,
                    yshift: this.config.labelOffset.normal,
                    font: {
                        size: this.config.labelFontSize,
                        color: this.config.titleColor
                    }
                });
                markerSizes[lastIndex] = markerSize + 3;
                if (showColoredAnnotations) {
                    markerColors[lastIndex] = this.config.annotationColor;
                }

                // 最大值（勾选显示最大值，且最大值不是最新值，则标注最大值）
                if (showNavMax && maxIndex !== lastIndex) {
                    annotations.push({
                        x: xValues[maxIndex],
                        y: values[maxIndex],
                        text: values[maxIndex].toString(),
                        showarrow: false,
                        yshift: this.config.labelOffset.max,
                        font: {
                            size: this.config.labelFontSize,
                            color: this.config.titleColor
                        }
                    });
                    markerSizes[maxIndex] = markerSize + 3;
                    if (showColoredAnnotations) {
                        markerColors[maxIndex] = this.config.annotationColor;
                    }
                }

                // 勾选显示起点值
                if (showNavStart) {
                    annotations.push({
                        x: xValues[0],
                        y: values[0],
                        text: values[0].toString(),
                        showarrow: false,
                        yshift: this.config.labelOffset.min,
                        font: {
                            size: this.config.labelFontSize,
                            color: this.config.titleColor
                        }
                    });
                    markerSizes[0] = markerSize + 3;
                    if (showColoredAnnotations) {
                        markerColors[0] = this.config.annotationColor;
                    }
                }

                trace.marker = {
                    size: markerSizes,
                    color: markerColors,
                    opacity: 1,
                    line: {
                        width: 0
                    }
                };
                layout.annotations = annotations;
            }

            Plotly.newPlot('navChart', [trace], layout, {displayModeBar: false});
        },

        renderYieldChart(data, yieldType) {
            const showYieldMax = document.getElementById('showYieldMax').checked;
            const showYieldMin = document.getElementById('showYieldMin').checked;
            const showColoredAnnotations = document.getElementById('showColoredAnnotations').checked;
            const markerSize = parseFloat(document.getElementById('markerSize').value) || this.config.markerSize;
            const yieldMargin = parseFloat(document.getElementById('yieldMargin').value) || 1.0;
            
            const values = data.map(d => d[yieldType]).filter(v => v !== undefined && v !== null);
            const dates = data.map(d => d['日期']);
            const meanValue = values.reduce((a, b) => a + b, 0) / values.length;

            // 计算Y轴范围
            const yMin = Math.min(...values);
            const yMax = Math.max(...values);
            const yRange = (yMax - yMin) * yieldMargin;

            const xValues = Array.from({length: dates.length}, (_, i) => i);
            
            // 零值线和平均值线
            const zeroLine = {
                x: xValues,
                y: Array(dates.length).fill(0),
                type: 'scatter',
                mode: 'lines',
                line: {
                    color: 'gray',
                    width: 1,
                    dash: '1px'
                },
                hoverinfo: 'none',
                showlegend: false
            };

            const meanLine = {
                x: xValues,
                y: Array(dates.length).fill(meanValue),
                type: 'scatter',
                mode: 'lines',
                line: {
                    color: 'gray',
                    width: 1,
                    dash: '1px'
                },
                hoverinfo: 'none',
                showlegend: false
            };

            const trace = {
                x: xValues,
                y: values,
                type: 'scatter',
                mode: 'lines+markers',
                line: {
                    color: this.config.lineColor,
                    width: this.config.lineWidth
                },
                marker: {
                    size: Array(dates.length).fill(markerSize),
                    color: this.config.lineColor,
                    opacity: 1,
                    line: {
                        width: 0
                    }
                },
                showlegend: false
            };
    
            const layout = {
                title: {
                    text: yieldType,
                    font: {
                        size: 16,
                        color: this.config.titleColor
                },
                    x: 1,  // 标题靠右对齐
                    xanchor: 'right'
                },
                xaxis: {
                    tickmode: 'array',
                    tickvals: this.getOptimalTicks(dates),
                    ticktext: this.getOptimalTicks(dates).map(i => this.formatDate(dates[i])),
                    tickangle: -45,
                    tickfont: {
                        color: this.config.titleColor,
                        size: 11  // 刻度字体大小
                    },
                    showgrid: true,
                    gridcolor: this.config.gridColor,
                    gridwidth: 1,
                    zeroline: false,
                    showline: true,
                    linecolor: this.config.titleColor,
                    linewidth: 1,
                    tickcolor: this.config.titleColor,
                    tickwidth: 1
                },
                yaxis: {
                    range: [yMin - yRange/2, yMax + yRange/2],  // 使用收益率Y轴边距比例
                    showgrid: false,
                    zeroline: false,
                    showline: false,
                    showticklabels: false
                },
                plot_bgcolor: 'white',
                paper_bgcolor: 'white',
                margin: { t: 30, r: 10, b: 80, l: 10 }  // 减小顶部边距
            };

            // 添加标注
            if (values.length > 0) {
                const annotations = [];
                const markerSizes = Array(dates.length).fill(markerSize);
                const markerColors = Array(dates.length).fill(this.config.lineColor);
                const lastIndex = values.length - 1;
                const maxIndex = values.indexOf(Math.max(...values));
                const minIndex = values.indexOf(Math.min(...values));

                // 始终标注最新值
                annotations.push({
                    x: xValues[lastIndex],
                    y: values[lastIndex],
                    text: (values[lastIndex] * 100).toFixed(2) + '%',
                    showarrow: false,
                    yshift: this.config.labelOffset.normal,
                    font: {
                        size: this.config.labelFontSize,
                        color: this.config.titleColor
                    }
                });
                markerSizes[lastIndex] = markerSize + 3;
                if (showColoredAnnotations) {
                    markerColors[lastIndex] = this.config.annotationColor;
                }

                // 如果选中了显示最大值，且最大值不是最新值，则标注最大值
                if (showYieldMax && maxIndex !== lastIndex) {
                    annotations.push({
                        x: xValues[maxIndex],
                        y: values[maxIndex],
                        text: (values[maxIndex] * 100).toFixed(2) + '%',
                        showarrow: false,
                        yshift: this.config.labelOffset.max,
                        font: {
                            size: this.config.labelFontSize,
                            color: this.config.titleColor
                        }
                    });
                    markerSizes[maxIndex] = markerSize + 3;
                    if (showColoredAnnotations) {
                        markerColors[maxIndex] = this.config.annotationColor;
                    }
                }

                // 最小值标注
                if (showYieldMin && minIndex !== lastIndex) {
                    annotations.push({
                        x: xValues[minIndex],
                        y: values[minIndex],
                        text: (values[minIndex] * 100).toFixed(2) + '%',
                        showarrow: false,
                        yshift: this.config.labelOffset.min,
                        font: {
                            size: this.config.labelFontSize,
                            color: this.config.titleColor
                        }
                    });
                    markerSizes[minIndex] = markerSize + 3;
                    if (showColoredAnnotations) {
                        markerColors[minIndex] = this.config.annotationColor;
                    }
                }

                // 添加平均值和零值标注
                annotations.push({
                    x: xValues[0],
                    y: 0,
                    text: '0.00%',  // 统一格式
                    showarrow: false,
                    xshift: -30,
                    font: {
                        size: this.config.auxiliaryLabelSize,  // 使用较小的字体
                        color: 'gray'
                    }
                });

                annotations.push({
                    x: xValues[0],
                    y: meanValue,
                    text: (meanValue * 100).toFixed(2) + '%',
                    showarrow: false,
                    xshift: -30,
                    font: {
                        size: this.config.auxiliaryLabelSize,  // 使用较小的字体
                        color: 'gray'
                    }
                });

                trace.marker = {
                    size: markerSizes,
                    color: markerColors,
                    opacity: 1,
                    line: {
                        width: 0
                    }
                };
                layout.annotations = annotations;
            }

            Plotly.newPlot('yieldChart', [trace, meanLine, zeroLine], layout, {displayModeBar: false});
        }
    };
    
    // UI控制模块
    const UIController = {
        init() {
            this.initEventListeners();
            this.initDateRanges();
            this.checkStoredData();
        },
    
        initEventListeners() {
            // 文件上传
            document.getElementById('fileInput').addEventListener('change', function(e) {
                const file = e.target.files[0];
                if (!file || !file.name.endsWith('.csv')) {
                    alert('请上传CSV格式的文件');
                    e.target.value = '';
                    return;
                }

                const statusElement = document.getElementById('dataStatus');
                statusElement.innerHTML = '正在处理数据...';
                const controller = UIController;

                const reader = new FileReader();
                reader.onload = function(event) {
                    // 使用 Promise 链来处理数据和 UI 更新
                    Promise.resolve().then(async () => {
                        try {
                            const csvText = event.target.result;
                            const lines = csvText.split(/\r\n|\n/).filter(line => line.trim());
                            
                            if (lines.length < 2) {
                                throw new Error('CSV文件中没有数据');
                            }

                            const headers = lines[0].split(',').map(h => h.trim());
                            const requiredColumns = ['产品简称', '日期', '净值', '7日年化收益(%)', '30日年化收益(%)', '近三月年化收益(%)'];
                            
                            const missingColumns = requiredColumns.filter(col => !headers.includes(col));
                            if (missingColumns.length > 0) {
                                throw new Error(`缺少必要的列: ${missingColumns.join(', ')}`);
                            }

                            const data = [];
                            for (let i = 1; i < lines.length; i++) {
                                const values = lines[i].split(',').map(v => v.trim());
                                if (values.length === headers.length) {
                                    const row = {};
                                    headers.forEach((header, index) => {
                                        const value = values[index];
                                        if (header === '日期') {
                                            row[header] = new Date(value.replace(/(\d{4})[/-](\d{2})[/-](\d{2})/, '$1/$2/$3'));
                                        } else if (['净值', '7日年化收益(%)', '30日年化收益(%)', '近三月年化收益(%)'].includes(header)) {
                                            row[header] = parseFloat(value) || 0;
                                        } else {
                                            row[header] = value;
                                        }
                                    });
                                    if (!isNaN(row['日期'].getTime())) {
                                        data.push(row);
                                    }
                                }
                            }

                            if (data.length === 0) {
                                throw new Error('没有有效的数据记录');
                            }

                            // 保存数据
                            await DataManager.saveData(data);
                            statusElement.innerHTML = '数据保存成功，正在更新界面...';

                            // 更新 UI
                            await controller.updateUI();
                } catch (error) {
                            console.error('数据处理失败:', error);
                            statusElement.innerHTML = `
                                <span style="color: #dc3545;">数据处理失败: ${error.message}</span><br>
                                <small style="color: #666;">请检查文件格式是否正确</small>
                            `;
                            controller.disableControls();
                        }
                    });
                };

                reader.onerror = function() {
                    statusElement.innerHTML = '文件读取失败，请重试';
                    controller.disableControls();
                };

                reader.readAsText(file);
                e.target.value = '';
            });
    
            // 清除数据
            document.getElementById('clearData').addEventListener('click', async () => {
                if (confirm('确定要清除所有数据吗？')) {
                    await DataManager.clearData();
                    Plotly.purge('navChart');
                    Plotly.purge('yieldChart');
                    document.getElementById('navChart').innerHTML = '';
                    document.getElementById('yieldChart').innerHTML = '';
                    this.disableControls();
                    await this.updateUI();
                }
            });
    
            // 制图按钮事件监听
            document.getElementById('generateCharts').addEventListener('click', async () => {
                try {
                    const data = await DataManager.loadData();
                    if (!data) {
                        alert('没有可用的数据，请先上传数据文件');
                        return;
                    }

                    const params = this.getParameters();
                    if (!params.productName) {
                        alert('请选择产品');
                        return;
                    }

                    console.log('开始生成图表，参数:', params);
                    
                    const filteredData = DataProcessor.filterData(data, params);
                    if (filteredData.length === 0) {
                        alert('所选时间范围内没有数据');
                        return;
                    }

                    // 更新图表
                    ChartRenderer.renderNavChart(filteredData);
                    ChartRenderer.renderYieldChart(filteredData, params.yieldType);
                    
                    console.log('图表生成完成');
                } catch (error) {
                    console.error('生成图表失败:', error);
                    alert('生成图表失败: ' + error.message);
                }
            });

            // 修改参数变更事件，移除自动更新图表
            ['productSelect', 'dateRange', 'recentDays', 'yieldType'].forEach(id => {
                document.getElementById(id).addEventListener('change', () => {
                    // 仅在选择产品时更新日期范围
                    if (id === 'productSelect') {
                        this.updateDateRangeForProduct();
                    }
                });
            });
    
            // 日期范围类型切换
            document.getElementById('dateRange').addEventListener('change', (e) => {
                const isCustom = e.target.value === 'custom';
                document.getElementById('recentDaysGroup').style.display = isCustom ? 'none' : 'block';
                document.getElementById('dateRangeGroup').style.display = isCustom ? 'block' : 'none';
            });
    
            // 日期选择器变更
            ['startDate', 'endDate'].forEach(id => {
                document.getElementById(id).addEventListener('change', () => this.updateCharts());
            });

            // 显示选项的事件监听
            ['showNavStart', 'showNavMax', 'showYieldMax', 'showYieldMin'].forEach(id => {
                document.getElementById(id).addEventListener('change', () => this.updateCharts());
            });

            // 重置按钮事件监听
            document.getElementById('resetParams').addEventListener('click', () => {
                this.resetParameters();
            });
        },
    
        initDateRanges() {
            const today = new Date();
            const threeMonthsAgo = new Date();
            threeMonthsAgo.setMonth(today.getMonth() - 3);

            document.getElementById('startDate').value = threeMonthsAgo.toISOString().split('T')[0];
            document.getElementById('endDate').value = today.toISOString().split('T')[0];
            document.getElementById('recentDays').value = '90';
        },
    
        async checkStoredData() {
            const data = await DataManager.loadData();
            if (data) {
                await this.updateUI();
                this.enableControls();
            }
        },
    
        async updateUI() {
            try {
                const data = await this.updateChartData().catch(error => {
                    document.getElementById('dataStatus').innerHTML = `
                        <span style="color: #dc3545;">检查数据源文件: ${error.message}</span><br>
                        <small style="color: #666;">请检查数据文件格式是否正确</small>
                    `;
                    this.disableControls();
                    return null;
                });

            if (!data) return;
    
                // Safari 使用简化版本的界面更新，但保留产品选择功能
                if (isSafari()) {
                    const updateTime = await DataManager.getUpdateTime();
                    const products = DataProcessor.getProductList(data);
                    
                    document.getElementById('dataStatus').innerHTML = `
                        数据源状态：<br>
                        <small style="color: #28a745;">
                            · 数据更新时间：${updateTime ? updateTime.toLocaleString() : '未知'}
                        </small>
                    `;
    
            // 更新产品选择器
            const productSelect = document.getElementById('productSelect');
                    productSelect.innerHTML = products.length > 0 
                        ? `<option value="">请选择产品</option>${products.map(p => `<option value="${p}">${p}</option>`).join('')}`
                        : '<option value="">没有可用产品</option>';

                    this.enableControls();
                    return;
                }

                // Chrome 保持原有的完整功能
                await this.updateDateInfo(data);
                this.renderCharts();
                this.enableControls();

            } catch (error) {
                console.error('UI更新失败:', error);
                document.getElementById('dataStatus').innerHTML = `
                    <span style="color: #dc3545;">界面更新失败: ${error.message}</span><br>
                    <small style="color: #666;">请刷新页面重试</small>
                `;
                this.disableControls();
            }
        },
    
        enableControls() {
            const productSelect = document.getElementById('productSelect');
            productSelect.disabled = false;
            productSelect.classList.remove('disabled');
            document.getElementById('generateCharts').disabled = false;
            
            // 确保选择器可以交互
            productSelect.style.pointerEvents = 'auto';
            productSelect.style.backgroundColor = '#fff';
            
            console.log('Controls enabled');
        },
    
        disableControls() {
            const productSelect = document.getElementById('productSelect');
            productSelect.disabled = true;
            productSelect.classList.add('disabled');
            document.getElementById('generateCharts').disabled = true;
            
            // 确保选择器不可交互
            productSelect.style.pointerEvents = 'none';
            productSelect.style.backgroundColor = '#f0f0f0';
            
            console.log('Controls disabled');
        },
    
        getParameters() {
            const params = {
                productName: document.getElementById('productSelect').value,
                dateRange: document.getElementById('dateRange').value,
                recentDays: parseInt(document.getElementById('recentDays').value),
                startDate: document.getElementById('startDate').value,
                endDate: document.getElementById('endDate').value,
                yieldType: document.getElementById('yieldType').value
            };

            // 添加检查
            const validProducts = ['蓝色宝', '收瑞盈'];
            const isValidProduct = validProducts.some(name => params.productName.includes(name));
            if (!isValidProduct) {
                throw new Error('invalid_product');
            }

            // 验证参数
            if (!params.productName) {
                throw new Error('请选择产品');
            }

            if (params.dateRange === 'recent') {
                if (!params.recentDays || params.recentDays <= 0) {
                    throw new Error('请输入有效的最近天数');
                }
            }

            if (params.dateRange === 'custom') {
                if (!params.startDate || !params.endDate) {
                    throw new Error('请选择完整的日期范围');
                }
                if (new Date(params.startDate) > new Date(params.endDate)) {
                    throw new Error('开始日期不能晚于结束日期');
                }
            }

            return params;
        },
    
        async updateCharts() {
            const data = await DataManager.loadData();
            if (!data) return;
    
            try {
                const params = this.getParameters();
                if (!params.productName) {
                    alert('请选择产品');
                    return;
                }
    
                const filteredData = DataProcessor.filterData(data, params);
                if (filteredData.length === 0) {
                    alert('所选时间范围内没有数据');
                    return;
                }
    
                ChartRenderer.renderNavChart(filteredData);
                ChartRenderer.renderYieldChart(filteredData, params.yieldType);
            } catch (error) {
                if (error.message !== 'invalid_product') {
                    alert(error.message);
                }
                return;
            }
        },

        // 根据选中产品的数据更新日期范围
        async updateDateRangeForProduct() {
            const data = await DataManager.loadData();
            const productName = document.getElementById('productSelect').value;
            if (!data || !productName) return;

            const productData = data.filter(row => row['产品简称'] === productName);
            if (productData.length === 0) return;

            // 获取该产品的数据日期范围
            const dates = productData.map(row => row['日期']);
            const minDate = new Date(Math.min(...dates));
            const maxDate = new Date(Math.max(...dates));

            // 更新日期选择器的范围
            document.getElementById('startDate').value = minDate.toISOString().split('T')[0];
            document.getElementById('endDate').value = maxDate.toISOString().split('T')[0];
        },

        // 修改重置参数方法
        resetParameters() {
            // 重置所有参数到默认值
            document.getElementById('dateRange').value = 'recent';
            document.getElementById('recentDays').value = '90';
            document.getElementById('yieldType').value = '7日年化收益(%)';
            document.getElementById('yieldMargin').value = '1.0';
            document.getElementById('navMarginX').value = '0.1';
            document.getElementById('markerSize').value = '6';
            document.getElementById('xAxisTicks').value = '8';  // 添加X轴刻度数量的重置
            
            // 重置显示选项
            document.getElementById('showNavStart').checked = false;
            document.getElementById('showNavMax').checked = false;
            document.getElementById('showYieldMax').checked = true;
            document.getElementById('showYieldMin').checked = true;
            document.getElementById('showColoredAnnotations').checked = false;

            // 重置日期范围显示
            document.getElementById('recentDaysGroup').style.display = 'block';
            document.getElementById('dateRangeGroup').style.display = 'none';
            
            // 清除图表
            Plotly.purge('navChart');
            Plotly.purge('yieldChart');
            
            // 清除图表容器内容
            document.getElementById('navChart').innerHTML = '';
            document.getElementById('yieldChart').innerHTML = '';
            
            // 重置配置对象中的值
            ChartRenderer.config.xAxisTicks = 8;  // 确保配置对象也被重置
            
            // 保持当前选中的产品
            const currentProduct = document.getElementById('productSelect').value;
            if (currentProduct) {
                // 重新生成图表
                this.updateCharts();
            }
        },

        // 拆分 UI 更新逻辑为多个小函数
        async updateChartData() {
            const data = await DataManager.loadData();
            if (!data) {
                throw new Error('没有数据可显示');
            }
            return data;
        },

        async updateDateInfo(data) {
            try {
                // 更新数据状态显示
                const updateTime = await DataManager.getUpdateTime();
                const products = DataProcessor.getProductList(data);
                const latestDate = new Date(Math.max(...data.map(row => new Date(row['日期']))));
                
                document.getElementById('dataStatus').innerHTML = `
                    数据源状态：<br>
                    <small style="color: #28a745;">
                        · 数据总量：${data.length} 条记录<br>
                        · 产品数量：${products.length} 个<br>
                        · 最新数据日期：${latestDate.toLocaleDateString('zh-CN')}<br>
                        · 数据更新时间：${updateTime ? updateTime.toLocaleString() : '未知'}
                    </small>
                `;

                // 更新产品选择器
                const productSelect = document.getElementById('productSelect');
                const currentProduct = productSelect.value;
                
                productSelect.innerHTML = products.length > 0 
                    ? `<option value="">请选择产品</option>${products.map(p => `<option value="${p}">${p}</option>`).join('')}`
                    : '<option value="">没有可用产品</option>';

                if (currentProduct && products.includes(currentProduct)) {
                    productSelect.value = currentProduct;
                    this.updateDateRangeForProduct();
                }
            } catch (error) {
                console.error('更新日期信息失败:', error);
                throw error;
            }
        },

        renderCharts() {
            // 清空图表
            Plotly.purge('navChart');
            Plotly.purge('yieldChart');
            document.getElementById('navChart').innerHTML = '';
            document.getElementById('yieldChart').innerHTML = '';
        }
    };
    
    // 初始化应用
    document.addEventListener('DOMContentLoaded', () => {
        UIController.init();
    });

    // 添加显示选项的事件监听器
    document.addEventListener('DOMContentLoaded', () => {
        const checkboxes = document.querySelectorAll('.checkbox-group input[type="checkbox"]');
        checkboxes.forEach(checkbox => {
            checkbox.addEventListener('change', () => {
                UIController.updateCharts();
            });
        });
    });

    // 添加浏览器检测函数
    const isSafari = () => {
        return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
    };

    // 修改日期范围选择的样式和默认值
    document.addEventListener('DOMContentLoaded', function() {
        // 设置默认起始日期为2024年8月1日
        document.getElementById('startDate').value = '2024-08-01';
        
        // 添加样式以对齐日期选择框
        const style = document.createElement('style');
        style.textContent = `
            .date-range-inputs {
                margin-left: 0;  /* 确保与时间范围选择框对齐 */
                padding-left: 0;
            }
            #startDate, #endDate {
                margin-top: 5px;
            }
        `;
        document.head.appendChild(style);
    });

    // 添加按钮颜色
    const buttonStyle = document.createElement('style');
    buttonStyle.textContent = `
        .btn-primary {
            background-color: #2952A3 !important;  /* 使用蓝色 */
            border-color: #2952A3 !important;
        }
        .btn-primary:hover {
            background-color: #1e3f7d !important;  /* 稍微深一点的蓝色用于悬停效果 */
            border-color: #1e3f7d !important;
        }
    `;
    document.head.appendChild(buttonStyle);
    </script>
    
